home *** CD-ROM | disk | FTP | other *** search
/ Programmer Plus 2007 / Programmer-Plus-2007.iso / Programming / XML Utilities / Professional Programmer XSL IDE / Xselerator25.msi / Data.Cab / F25618_SimpleStackedBar.xsl < prev    next >
Encoding:
Extensible Markup Language  |  2002-03-03  |  14.4 KB  |  301 lines

  1. <?xml version="1.0"?>
  2. <xsl:stylesheet version="1.0" 
  3.   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  4.   xmlns:svg="http://www.w3.org/2000/svg">
  5. <xsl:output method="xml" indent="yes"/>
  6. <!-- global variable for bar width -->
  7. <xsl:variable name="bar_width" select="number(80)"/>
  8. <!-- spacing between bars -->
  9. <xsl:variable name="bar_spacing" select="number(10)"/>
  10. <!-- graph height scaling -->
  11. <xsl:variable name="graph_height" select="number(500)"/>
  12. <xsl:variable name="left_margin" select="number(100)"/>
  13.  
  14. <!-- key used for distinct product ids -->
  15. <xsl:key name="kDistinctProdIDs" match="product_sales" use="@product_id"/>
  16. <!-- key for each prod id within week -->
  17. <xsl:key name="kWeeksProd" match="/sales_summary/weekly_sales/product_sales" use="concat(../@week_no,'||',@product_id)"/>
  18.  
  19. <xsl:variable name="DistinctProducts" select="/sales_summary/weekly_sales/product_sales[generate-id() = generate-id(key('kDistinctProdIDs',@product_id))]"/>
  20.  
  21. <xsl:template match="/">
  22.   <!-- body root of svg -->
  23.   <svg:svg id="body" viewBox="0 0 -100 {$graph_height + 120}">
  24.     <!-- script for showing/hiding actual value hints on bars -->
  25.       <script type="text/ecmascript"><![CDATA[
  26.       function DoOnOver(evt,HintId){
  27.         // get document...
  28.         var target = evt.getTarget();
  29.         var doc = target.getOwnerDocument();
  30.         // get the hint graphic...
  31.         var Hint = doc.getElementById(HintId);
  32.         // make hint graphic visible...
  33.         Hint.setAttribute('style', 'visibility:visible');
  34.       }
  35.       function DoOnOut(evt,HintId){
  36.         // get document...
  37.         var target = evt.getTarget();
  38.         var doc = target.getOwnerDocument();
  39.         // get the hint graphic...
  40.         var Hint = doc.getElementById(HintId);
  41.         // make hint graphic visible...
  42.         Hint.setAttribute('style', 'visibility:hidden');
  43.       }
  44.       ]]></script>
  45.     <!-- graphic title -->
  46.     <svg:title>Weekly Sales Values</svg:title>
  47.     <!-- main graphic -->
  48.     <svg:g id="barChart" transform="translate(10, 10)" fill-rule="nonzero" clip-rule="nonzero" stroke="none" class="legend"
  49.       stroke-width="1" stroke-linecap="square" stroke-miterlimit="1" style="text-anchor:start" shape-rendering="crispEdges">
  50.       <!-- apply data -->
  51.       <xsl:apply-templates select="sales_summary"/>
  52.       <svg:text transform="matrix(2 0 0 2 {$left_margin} {$graph_height + 70})">Weekly Total Sales Values</svg:text>
  53.     </svg:g>
  54.   </svg:svg>
  55. </xsl:template>
  56.  
  57. <!-- main data template -->
  58. <xsl:template match="sales_summary">
  59.   <!-- calculate the number of weeks -->
  60.   <xsl:variable name="weeks_count" select="count(weekly_sales)"/>
  61.   <!-- calculate the maximum weekly sales value -->
  62.   <xsl:variable name="max_week_sales_value">
  63.     <xsl:for-each select="weekly_sales">
  64.       <xsl:sort select="sum(product_sales/@value)" data-type="number" order="descending"/>
  65.       <xsl:if test="position() = 1">
  66.         <xsl:value-of select="sum(product_sales/@value)"/>
  67.       </xsl:if>
  68.     </xsl:for-each>
  69.   </xsl:variable>
  70.   <!-- max graph height is max sales rounded to next 100 -->
  71.   <xsl:variable name="max_graph_height" select="floor(($max_week_sales_value + 99) div 100) * 100"/>
  72.   <!-- draw graph background -->
  73.   <xsl:call-template name="draw_graph">
  74.     <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
  75.     <xsl:with-param name="bar_count" select="$weeks_count"/>
  76.   </xsl:call-template>
  77.   <!-- draw the graph bars themselves from data -->
  78.   <xsl:apply-templates select="weekly_sales">
  79.     <xsl:sort select="@week_no" data-type="number"/>
  80.     <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
  81.     <xsl:with-param name="bar_count" select="$weeks_count"/>
  82.   </xsl:apply-templates>
  83. </xsl:template>
  84.  
  85. <!-- draw graph background and vertical scale/legends -->
  86. <xsl:template name="draw_graph">
  87.   <xsl:param name="max_graph_height"/>
  88.   <xsl:param name="bar_count"/>
  89.   <xsl:variable name="actual_width" select="($bar_count * ($bar_width + $bar_spacing)) + $bar_spacing"/>
  90.   <svg:g id="GridAndLegend" style="stroke:none;" shape-rendering="crispEdges" stroke-width="1">
  91.     <!-- back face and surrounding lines -->
  92.     <svg:path fill="lightgray" stroke="black" d="M {$left_margin},{$graph_height + 20} h{$actual_width} v-{$graph_height} h-{$actual_width} v{$graph_height}"/>
  93.     <!-- draw zero mark and legend -->
  94.     <svg:path fill="none" stroke="black" d="M {$left_margin - 10},{$graph_height + 20} h10"/>
  95.     <svg:text text-anchor="end" baseline-shift="-3" transform="matrix(1 0 0 1 {$left_margin - 15} {$graph_height + 20})">0</svg:text>
  96.     <!-- draw vertical lines and legends -->
  97.     <xsl:call-template name="draw_graph_vertical_legends">
  98.       <xsl:with-param name="max" select="$max_graph_height"/>
  99.       <xsl:with-param name="legend_threshold" select="$max_graph_height div $graph_height"/>
  100.       <xsl:with-param name="actual_width" select="$actual_width"/>
  101.     </xsl:call-template>
  102.     <!-- draw color box legends -->
  103.     <xsl:for-each select="$DistinctProducts">
  104.       <xsl:variable name="calculated_color">
  105.         <xsl:call-template name="calc_color">
  106.           <xsl:with-param name="rel_position" select="position()"/>
  107.         </xsl:call-template>
  108.       </xsl:variable>
  109.       <svg:path fill="{$calculated_color}" stroke="black" d="M {$left_margin + ((position() - 1) * 100)},{$graph_height + 90} h10 v-10 h-10 v10"/>
  110.       <svg:text transform="matrix(1.1 0 0 1.1 {$left_margin + ((position() - 1) * 100) + 13} {$graph_height + 90})">
  111.         <xsl:value-of select="@product_id"/>
  112.       </svg:text>
  113.     </xsl:for-each>
  114.   </svg:g>
  115. </xsl:template>
  116.  
  117. <!-- recursive template to draw vertical lines and legends -->
  118. <xsl:template name="draw_graph_vertical_legends">
  119.   <xsl:param name="max"/>
  120.   <xsl:param name="legend_threshold"/>
  121.   <xsl:param name="actual_width"/>
  122.   <!-- params used only during recursion -->
  123.   <xsl:param name="start" select="number(100)"/>
  124.   <xsl:param name="step" select="number(100)"/>
  125.   <xsl:param name="prev_marked_start" select="number(0)"/>
  126.   <!-- calculate actual vertical 'pixel' position for this mark and previous drawn mark -->
  127.   <xsl:variable name="prev_vert_posn" select="($prev_marked_start div $max) * $graph_height"/>
  128.   <xsl:variable name="vert_posn" select="($start div $max) * $graph_height"/>
  129.   <!-- work out whether this line is too close to previous line -->
  130.   <xsl:variable name="new_marked_start">
  131.     <xsl:choose>
  132.       <xsl:when test="($vert_posn - $prev_vert_posn) >= $legend_threshold">
  133.         <xsl:value-of select="$start"/>
  134.       </xsl:when>
  135.       <xsl:otherwise>
  136.         <xsl:value-of select="$prev_marked_start"/>
  137.       </xsl:otherwise>
  138.     </xsl:choose>
  139.   </xsl:variable>
  140.   <!-- only draw line and legend when not too close -->
  141.   <xsl:if test="$new_marked_start = $start">
  142.     <svg:path fill="none" stroke="black" d="M {$left_margin - 10},{$graph_height + 20 - floor($vert_posn)} h{$actual_width + 10}"/>
  143.     <svg:text text-anchor="end" baseline-shift="-3" transform="matrix(1 0 0 1 {$left_margin - 15} {$graph_height + 20 - floor($vert_posn)})">
  144.       <xsl:value-of select="$start"/>
  145.     </svg:text>
  146.   </xsl:if>
  147.   <!-- if not yet reached max then recurse call to this template -->
  148.   <xsl:if test="$start < $max">
  149.     <xsl:call-template name="draw_graph_vertical_legends">
  150.       <xsl:with-param name="max" select="$max"/>
  151.       <xsl:with-param name="legend_threshold" select="$legend_threshold"/>
  152.       <xsl:with-param name="actual_width" select="$actual_width"/>
  153.       <xsl:with-param name="start" select="$start + $step"/>
  154.       <xsl:with-param name="step" select="$step"/>
  155.       <xsl:with-param name="prev_marked_start" select="$new_marked_start"/>
  156.     </xsl:call-template>
  157.   </xsl:if>
  158. </xsl:template>
  159.  
  160. <!-- applied template that handles each week -->
  161. <!-- then calls routine for drawing stacked bars -->
  162. <xsl:template match="weekly_sales">
  163.   <xsl:param name="max_graph_height"/>
  164.   <xsl:param name="bar_count"/>
  165.   <!-- calc left position of this bar  -->
  166.   <xsl:variable name="bar_left" select="((position() - 1) * ($bar_width + $bar_spacing)) + $bar_spacing"/>
  167.   <!-- draw the bar -->
  168.   <svg:g id="weekbar_{@week_no}" onmouseover="DoOnOver(evt,'Hint_{@week_no}')" onmouseout="DoOnOut(evt,'Hint_{@week_no}')">
  169.     <xsl:call-template name="draw_bar_stacks">
  170.       <xsl:with-param name="week_no" select="@week_no"/>
  171.       <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
  172.       <xsl:with-param name="bar_left" select="$bar_left"/>
  173.     </xsl:call-template>
  174.   </svg:g>
  175.   <!-- draw the legend -->
  176.   <svg:text text-anchor="middle" transform="matrix(1.5 0 0 1.5 {$left_margin + $bar_left + ($bar_width div 2)} {$graph_height + 40})">
  177.     <xsl:text>Week </xsl:text>
  178.     <xsl:value-of select="@week_no"/>
  179.   </svg:text>
  180.   <!-- create the invisible hint info for the bar -->
  181.   <svg:g id="Hint_{@week_no}" style="visibility: hidden;" transform="matrix(1 0 0 1 {$left_margin + $bar_spacing + ($bar_count * ($bar_width + $bar_spacing)) + 20} 20)">
  182.     <!-- draw box around hint -->
  183.     <xsl:variable name="box_height" select="(count(product_sales) * 20) + 50"/>
  184.     <svg:path fill="none" stroke="black" d="M 0,0 h160 v{$box_height} h-160 v-{$box_height}"/>
  185.     <!-- draw centered week no at top -->
  186.     <svg:text text-anchor="middle" transform="matrix(1.5 0 0 1.5 80 20)">
  187.       <xsl:text>Week </xsl:text>
  188.       <xsl:value-of select="@week_no"/>
  189.     </svg:text>
  190.     <!-- draw legends and figures for breakdown of this week -->
  191.     <xsl:variable name="this_week" select="@week_no"/>
  192.     <xsl:for-each select="$DistinctProducts">
  193.       <xsl:variable name="calculated_color">
  194.         <xsl:call-template name="calc_color">
  195.           <xsl:with-param name="rel_position" select="position()"/>
  196.         </xsl:call-template>
  197.       </xsl:variable>
  198.       <svg:path fill="{$calculated_color}" stroke="black" d="M 10,{(position() * 20) + 20} h10 v-10 h-10 v10"/>
  199.       <svg:text transform="matrix(1.1 0 0 1.1 25 {(position() * 20) + 20})">
  200.         <xsl:value-of select="@product_id"/>
  201.       </svg:text>
  202.       <svg:text text-anchor="end" transform="matrix(1.1 0 0 1.1 150 {(position() * 20) + 20})">
  203.         <xsl:choose>
  204.           <xsl:when test="key('kWeeksProd',concat($this_week,'||',@product_id))">
  205.             <xsl:value-of select="format-number(key('kWeeksProd',concat($this_week,'||',@product_id))/@value,'$#,##0')"/>
  206.           </xsl:when>
  207.           <xsl:otherwise>(none)</xsl:otherwise>
  208.         </xsl:choose>
  209.       </svg:text>
  210.     </xsl:for-each>
  211.     <!-- week total -->
  212.     <svg:text text-anchor="end" transform="matrix(1.1 0 0 1.1 150 {$box_height - 10})">
  213.       <xsl:text>Total </xsl:text>
  214.       <xsl:value-of select="format-number(sum(product_sales/@value),'$#,##0')"/>
  215.     </svg:text>
  216.     
  217. <!--    
  218.     <svg:path fill="none" stroke="red" d="M {$left_margin - 10},{$graph_height + 20 - $bar_height} h{$bar_left + 10}"/>
  219.     <svg:text transform="matrix(1.5 0 0 1.5 {$left_margin + $bar_left + 2} {($graph_height + 35) - $bar_height})">
  220.       <xsl:value-of select="format-number($sales_value,'#,##0')"/>
  221.     </svg:text>
  222. -->
  223.   </svg:g>
  224. </xsl:template>
  225.  
  226. <!-- recursive template for drawing the stacked parts of each bar -->
  227. <!-- a recursive template is used because of need to track the    -->
  228. <!-- the height of previous bar parts on drawing each new part.   -->
  229. <!-- Also, the bar position for each part needs to be consistent  -->
  230. <!-- so that the bar color for each product is consistent.        -->
  231. <xsl:template name="draw_bar_stacks">
  232.   <xsl:param name="week_no"/>
  233.   <xsl:param name="max_graph_height"/>
  234.   <xsl:param name="bar_left"/>
  235.   <!-- params used only in recursion -->
  236.   <xsl:param name="on_product" select="number(1)"/>
  237.   <xsl:param name="prev_height" select="number(0)"/>
  238.   <!-- get the item for this part of the bar -->
  239.   <xsl:variable name="product_id" select="$DistinctProducts[$on_product]/@product_id"/>
  240.   <xsl:variable name="this_weeks_product" select="key('kWeeksProd',concat($week_no,'||',$product_id))"/>
  241.   <!-- calc the height of this bar part -->
  242.   <xsl:variable name="bar_height">
  243.     <xsl:choose>
  244.       <xsl:when test="$this_weeks_product">
  245.         <xsl:value-of select="floor(($this_weeks_product/@value div $max_graph_height) * $graph_height)"/>
  246.       </xsl:when>
  247.       <xsl:otherwise>0</xsl:otherwise>
  248.     </xsl:choose>
  249.   </xsl:variable>
  250.   <!-- only draw this part of stack if the product exists in this week -->
  251.   <xsl:if test="$this_weeks_product">
  252.     <!-- calc the color -->
  253.     <xsl:variable name="bar_color">
  254.       <xsl:call-template name="calc_color">
  255.         <xsl:with-param name="rel_position" select="$on_product"/>
  256.       </xsl:call-template>
  257.     </xsl:variable>
  258.     <svg:path fill="{$bar_color}" stroke="none" d="M {$bar_left + $left_margin},{$graph_height + 19 - $prev_height} h{$bar_width} v-{$bar_height} h-{$bar_width} v{$bar_height}"/>
  259.   </xsl:if>
  260.   <!-- do next product -->
  261.   <xsl:if test="$DistinctProducts[$on_product + 1]">
  262.     <xsl:call-template name="draw_bar_stacks">
  263.       <xsl:with-param name="week_no" select="$week_no"/>
  264.       <xsl:with-param name="max_graph_height" select="$max_graph_height"/>
  265.       <xsl:with-param name="bar_left" select="$bar_left"/>
  266.       <xsl:with-param name="on_product" select="$on_product + 1"/>
  267.       <xsl:with-param name="prev_height" select="$prev_height + $bar_height"/>
  268.     </xsl:call-template>
  269.   </xsl:if>
  270. </xsl:template>
  271.  
  272. <!-- called template for calculating colors -->
  273. <!-- this is a crude way of giving each bar a different color -->
  274. <!-- color is calculated according to current position() -->
  275. <xsl:template name="calc_color">
  276.   <xsl:param name="rel_position" select="number(0)"/>
  277.   <xsl:variable name="color_lightnesses" select="'F0D0B09070503010E0C0A08060402000'"/>
  278.   <xsl:text>#</xsl:text>
  279.   <!-- red part -->
  280.   <xsl:choose>
  281.     <xsl:when test="($rel_position mod 3) = 0">F0</xsl:when>
  282.     <xsl:otherwise>
  283.       <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
  284.     </xsl:otherwise>
  285.   </xsl:choose>
  286.   <!-- blue part -->
  287.   <xsl:choose>
  288.     <xsl:when test="($rel_position mod 3) = 1">F0</xsl:when>
  289.     <xsl:otherwise>
  290.       <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
  291.     </xsl:otherwise>
  292.   </xsl:choose>
  293.   <!-- green part -->
  294.   <xsl:choose>
  295.     <xsl:when test="($rel_position mod 3) = 2">F0</xsl:when>
  296.     <xsl:otherwise>
  297.       <xsl:value-of select="substring($color_lightnesses,(($rel_position mod 16) * 2)+1,2)"/>
  298.     </xsl:otherwise>
  299.   </xsl:choose>
  300. </xsl:template>
  301. </xsl:stylesheet>